<?php
// file_manager.php
// Simple but feature-rich file manager (Bahasa Indonesia).
// WARNING: Hanya gunakan pada server milik Anda dan pastikan akses aman (HTTPS, firewall, password kuat).

/*
How to use:
1. Upload this single PHP file to your server.
2. Edit $PASSWORD di bawah sebelum digunakan.
3. Akses via browser: https://your-server/file_manager.php
*/

// -------------------- Konfigurasi --------------------
$PASSWORD = 'ta091190'; // <-- Ganti ini! gunakan password kuat
$ALLOW_ROOT_NAV = true; // true = izinkan berpindah ke "/" (bergantung pada izin PHP)

// Optional: batasi path root untuk keamanan, contoh: '/var/www/html'
$OPTIONAL_BASE_PATH = null; // null = tidak dibatasi. Atur ke path absolut untuk membatasi.

// Set timezone untuk tampilan waktu
date_default_timezone_set('Asia/Jakarta');

// -------------------- End Konfigurasi --------------------

// --- Session & CSRF ---
session_start();
if (!isset($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(16));
}
function verify_csrf($token) {
    return isset($token) && hash_equals($_SESSION['csrf_token'], $token);
}

// --- Simple Auth ---
function is_logged_in() {
    return !empty($_SESSION['fileman_logged_in']) && $_SESSION['fileman_logged_in'] === true;
}
if (isset($_POST['action']) && $_POST['action'] === 'login') {
    $pw = $_POST['password'] ?? '';
    if (hash_equals($pw, $GLOBALS['PASSWORD'])) {
        $_SESSION['fileman_logged_in'] = true;
        header('Location: ' . $_SERVER['PHP_SELF']);
        exit;
    } else {
        $login_error = 'Password salah.';
    }
}
if (isset($_GET['action']) && $_GET['action'] === 'logout') {
    session_destroy();
    header('Location: ' . $_SERVER['PHP_SELF']);
    exit;
}

// --- Utility functions ---
function normalize_path($path) {
    // Convert to absolute and normalize .. and .
    $p = str_replace(['\\', '//'], '/', $path);
    $parts = array_filter(explode('/', $p), 'strlen');
    $absolutes = [];
    foreach ($parts as $part) {
        if ($part === '.') continue;
        if ($part === '..') {
            array_pop($absolutes);
        } else {
            $absolutes[] = $part;
        }
    }
    $res = ($path[0] === '/') ? '/' . implode('/', $absolutes) : implode('/', $absolutes);
    if ($res === '') $res = '/';
    return $res;
}
function resolve_path($base, $target) {
    if (strpos($target, '/') === 0) {
        $abs = normalize_path($target);
    } else {
        $abs = normalize_path($base . '/' . $target);
    }
    // optional base path enforcement
    if (!empty($GLOBALS['OPTIONAL_BASE_PATH'])) {
        $baseNorm = rtrim(realpath($GLOBALS['OPTIONAL_BASE_PATH']), '/');
        $real = @realpath($abs);
        if ($real === false || strpos($real, $baseNorm) !== 0) {
            return false;
        }
        return $real;
    }
    return $abs;
}
function path_for_display($p) {
    return htmlspecialchars($p, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}
function filesize_human($bytes) {
    if ($bytes < 1024) return $bytes . ' B';
    $units = ['KB','MB','GB','TB'];
    $u = -1;
    do {
        $bytes /= 1024;
        $u++;
    } while ($bytes >= 1024 && $u < count($units)-1);
    return round($bytes, 2) . ' ' . $units[$u];
}

// --- Determine current directory ---
$cwd = $_GET['path'] ?? getcwd();
if ($cwd === '') $cwd = getcwd();
$cwd = resolve_path(getcwd(), $cwd);
if ($cwd === false) {
    $cwd = getcwd();
}
if ($OPTIONAL_BASE_PATH) {
    $cwd = resolve_path($OPTIONAL_BASE_PATH, $cwd) ?: $OPTIONAL_BASE_PATH;
}

if (!$ALLOW_ROOT_NAV) {
    // Prevent navigating to "/" if disallowed
    if ($cwd === '/' ) {
        $cwd = getcwd();
    }
}

// --- Handle actions (requires login) ---
$messages = [];
$errors = [];

if (is_logged_in() && $_SERVER['REQUEST_METHOD'] === 'POST') {
    $action = $_POST['action'] ?? '';
    // verify CSRF for all state-changing requests
    if (!verify_csrf($_POST['csrf'] ?? '')) {
        $errors[] = 'Token CSRF tidak valid.';
    } else {
        try {
            switch ($action) {
                case 'upload':
                    if (!isset($_FILES['upload_file'])) { $errors[] = 'File tidak ditemukan.'; break; }
                    $destName = basename($_FILES['upload_file']['name']);
                    $targetPath = resolve_path($cwd, $destName);
                    if ($targetPath === false) { $errors[] = 'Gagal menentukan path tujuan.'; break; }
                    if (!move_uploaded_file($_FILES['upload_file']['tmp_name'], $targetPath)) {
                        $errors[] = 'Gagal mengupload file.';
                    } else {
                        $messages[] = 'File berhasil diupload.';
                    }
                    break;

                case 'mkdir':
                    $name = $_POST['dirname'] ?? '';
                    if ($name === '') { $errors[] = 'Nama folder kosong.'; break; }
                    $target = resolve_path($cwd, $name);
                    if ($target === false) { $errors[] = 'Path tidak valid.'; break; }
                    if (!mkdir($target, 0755, true)) { $errors[] = 'Gagal membuat folder.'; } else { $messages[] = 'Folder dibuat.'; }
                    break;

                case 'delete':
                    $target = $_POST['target'] ?? '';
                    $targetPath = resolve_path($cwd, $target);
                    if ($targetPath === false) { $errors[] = 'Path tidak valid.'; break; }
                    // delete file or directory (recursive)
                    function rrmdir($dir) {
                        if (!file_exists($dir)) return true;
                        if (!is_dir($dir)) return unlink($dir);
                        $items = scandir($dir);
                        foreach ($items as $item) {
                            if ($item === '.' || $item === '..') continue;
                            if (!rrmdir("$dir/$item")) return false;
                        }
                        return rmdir($dir);
                    }
                    if (!rrmdir($targetPath)) { $errors[] = 'Gagal menghapus.'; } else { $messages[] = 'Berhasil dihapus.'; }
                    break;

                case 'rename':
                    $old = $_POST['old'] ?? '';
                    $new = $_POST['new'] ?? '';
                    if ($old === '' || $new === '') { $errors[] = 'Nama lama/baru kosong.'; break; }
                    $oldp = resolve_path($cwd, $old);
                    $newp = resolve_path($cwd, $new);
                    if ($oldp === false || $newp === false) { $errors[] = 'Path tidak valid.'; break; }
                    if (!rename($oldp, $newp)) { $errors[] = 'Gagal mengganti nama.'; } else { $messages[] = 'Berhasil mengganti nama.'; }
                    break;

                case 'chmod':
                    $target = $_POST['target'] ?? '';
                    $mode = $_POST['mode'] ?? '';
                    $targetPath = resolve_path($cwd, $target);
                    if ($targetPath === false) { $errors[] = 'Path tidak valid.'; break; }
                    if (!preg_match('/^[0-7]{3,4}$/', $mode)) { $errors[] = 'Mode chmod harus berupa angka oktal (misal 0644 atau 755).'; break; }
                    $intmode = octdec($mode);
                    if (!chmod($targetPath, $intmode)) { $errors[] = 'Gagal mengubah chmod.'; } else { $messages[] = 'Berhasil mengubah permission.'; }
                    break;

                case 'savefile':
                    $file = $_POST['file'] ?? '';
                    $content = $_POST['content'] ?? '';
                    $targetPath = resolve_path($cwd, $file);
                    if ($targetPath === false) { $errors[] = 'Path tidak valid.'; break; }
                    if (!is_writable(dirname($targetPath))) {
                        // still allow if file exists and writable
                    }
                    if (file_put_contents($targetPath, $content) === false) { $errors[] = 'Gagal menyimpan file.'; } else { $messages[] = 'File disimpan.'; }
                    break;

                case 'copy':
                    $src = $_POST['src'] ?? '';
                    $dst = $_POST['dst'] ?? '';
                    $srcp = resolve_path($cwd, $src);
                    $dstp = resolve_path($cwd, $dst);
                    if ($srcp === false || $dstp === false) { $errors[] = 'Path tidak valid.'; break; }
                    if (is_dir($srcp)) {
                        // recursive copy
                        function rcopy($src, $dst) {
                            if (!file_exists($dst)) mkdir($dst, 0755, true);
                            $items = scandir($src);
                            foreach ($items as $item) {
                                if ($item === '.' || $item === '..') continue;
                                $s = "$src/$item";
                                $d = "$dst/$item";
                                if (is_dir($s)) { if (!rcopy($s,$d)) return false; }
                                else if (!copy($s, $d)) return false;
                            }
                            return true;
                        }
                        if (!rcopy($srcp, $dstp)) $errors[] = 'Gagal menyalin folder.'; else $messages[] = 'Folder disalin.';
                    } else {
                        if (!copy($srcp, $dstp)) $errors[] = 'Gagal menyalin file.'; else $messages[] = 'File disalin.';
                    }
                    break;

                case 'move':
                    $src = $_POST['src'] ?? '';
                    $dst = $_POST['dst'] ?? '';
                    $srcp = resolve_path($cwd, $src);
                    $dstp = resolve_path($cwd, $dst);
                    if ($srcp === false || $dstp === false) { $errors[] = 'Path tidak valid.'; break; }
                    if (!rename($srcp, $dstp)) $errors[] = 'Gagal memindahkan.'; else $messages[] = 'Berhasil dipindah.';
                    break;

                default:
                    $errors[] = 'Aksi tidak dikenali.';
            }
        } catch (Exception $e) {
            $errors[] = 'Error: ' . $e->getMessage();
        }
    } // end csrf check
    // after POST redirect to avoid resubmission
    if (!empty($messages) || !empty($errors)) {
        $qs = [];
        if (isset($_GET['path'])) $qs['path'] = $_GET['path'];
        header('Location: ' . $_SERVER['PHP_SELF'] . (count($qs) ? ('?'.http_build_query($qs)) : ''));
        exit;
    }
}

// --- Directory listing ---
$entries = [];
if (is_dir($cwd)) {
    $dirItems = scandir($cwd);
    foreach ($dirItems as $item) {
        if ($item === '.') continue;
        $full = $cwd . DIRECTORY_SEPARATOR . $item;
        $is_dir = is_dir($full);
        $entries[] = [
            'name' => $item,
            'path' => $full,
            'is_dir' => $is_dir,
            'size' => $is_dir ? 0 : (is_file($full) ? filesize($full) : 0),
            'mtime' => filemtime($full),
            'perms' => substr(sprintf('%o', fileperms($full)), -4),
        ];
    }
    usort($entries, function($a,$b){
        if ($a['is_dir'] && !$b['is_dir']) return -1;
        if (!$a['is_dir'] && $b['is_dir']) return 1;
        return strcasecmp($a['name'],$b['name']);
    });
}

// --- Breadcrumb builder ---
function build_breadcrumbs($cwd) {
    $parts = explode('/', rtrim($cwd, '/'));
    $crumbs = [];
    $acc = ($cwd[0] === '/') ? '' : '';
    if ($cwd === '/' || $parts[0] === '') {
        // absolute path
        $acc = '/';
        $crumbs[] = ['label' => '/', 'path' => '/'];
        $sub = array_filter(explode('/', ltrim($cwd, '/')));
        $p = '';
        foreach ($sub as $s) {
            $p = $p . '/' . $s;
            $crumbs[] = ['label' => $s, 'path' => $p];
        }
    } else {
        $p = '';
        foreach ($parts as $part) {
            $p = $p === '' ? $part : $p . '/' . $part;
            $crumbs[] = ['label' => $part, 'path' => $p];
        }
    }
    return $crumbs;
}
$breadcrumbs = build_breadcrumbs($cwd);

// --- Helper for download/edit links relative path ---
function relpath($abs) {
    // if inside optional base path, create relative path from that
    if (!empty($GLOBALS['OPTIONAL_BASE_PATH'])) {
        $base = rtrim(realpath($GLOBALS['OPTIONAL_BASE_PATH']), '/');
        $real = realpath($abs);
        if ($real !== false && strpos($real, $base) === 0) {
            $rel = substr($real, strlen($base));
            if ($rel === '') $rel = '/';
            return $rel;
        }
    }
    return $abs;
}

// --- Render HTML ---
?><!doctype html>
<html lang="id">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>File Manager</title>
<style>
:root{--bg:#0f1720;--card:#0b1220;--muted:#98a2b3;--accent:#62d6a7;--danger:#ff6b6b}
*{box-sizing:border-box}body{margin:0;font-family:Inter,ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,"Helvetica Neue",Arial;background:linear-gradient(180deg,#071226 0%, #0b1220 100%);color:#e6eef6}
.container{max-width:1100px;margin:20px auto;padding:20px}
.header{display:flex;justify-content:space-between;align-items:center;margin-bottom:14px}
.brand{display:flex;align-items:center;gap:12px}
.logo{width:44px;height:44px;border-radius:8px;background:linear-gradient(135deg,#2dd4bf,#60a5fa);display:flex;align-items:center;justify-content:center;color:#05203a;font-weight:700;font-size:18px}
.h1{font-size:20px;color:#e6eef6;margin:0}
.controls{display:flex;gap:8px;align-items:center}
.card{background:rgba(255,255,255,0.03);padding:14px;border-radius:10px;box-shadow:0 6px 18px rgba(2,6,23,0.6)}
.breadcrumbs{font-size:13px;color:var(--muted);margin-bottom:12px}
.table{width:100%;border-collapse:collapse}
.table th,.table td{padding:8px 10px;text-align:left;font-size:14px}
.table th{color:var(--muted);font-weight:600;border-bottom:1px solid rgba(255,255,255,0.04)}
.row:hover{background:rgba(255,255,255,0.02)}
.btn{display:inline-block;padding:8px 10px;border-radius:8px;text-decoration:none;color:#04202a;background:linear-gradient(180deg,#a7f3d0,#62d6a7);font-weight:600}
.btn-ghost{background:transparent;border:1px solid rgba(255,255,255,0.04);color:var(--muted);padding:6px 8px;border-radius:8px}
.small{font-size:13px;color:var(--muted)}
.form-inline{display:flex;gap:8px;align-items:center;flex-wrap:wrap}
.input, textarea, select{background:transparent;border:1px solid rgba(255,255,255,0.06);padding:8px 10px;border-radius:8px;color:#e6eef6}
textarea{min-height:200px;font-family:monospace}
.actions{display:flex;gap:6px;flex-wrap:wrap}
.alert{padding:10px;border-radius:8px;margin-bottom:10px}
.alert.ok{background:linear-gradient(180deg, rgba(98,214,167,0.12), rgba(98,214,167,0.06));color:var(--accent)}
.alert.err{background:linear-gradient(180deg, rgba(255,107,107,0.10), rgba(255,107,107,0.04));color:var(--danger)}
.footer{color:var(--muted);font-size:13px;margin-top:12px;text-align:right}
.icon{opacity:0.9;font-weight:700}
</style>
</head>
<body>
<div class="container">
    <div class="header">
        <div class="brand">
            <div class="logo">FM</div>
            <div>
                <div class="h1">PHP File Manager</div>
                <div class="small">Direktori: <strong><?php echo path_for_display($cwd); ?></strong></div>
            </div>
        </div>
        <div class="controls">
            <?php if (is_logged_in()): ?>
                <a class="btn-ghost" href="<?php echo $_SERVER['PHP_SELF']; ?>?action=logout">Logout</a>
            <?php endif; ?>
        </div>
    </div>

    <?php if (!is_logged_in()): ?>
        <div class="card">
            <form method="post" class="form-inline">
                <input type="hidden" name="action" value="login">
                <input class="input" type="password" name="password" placeholder="Masukkan password" required>
                <button class="btn" type="submit">Login</button>
                <?php if (!empty($login_error)): ?><div class="alert err" style="margin-top:8px;"><?php echo htmlspecialchars($login_error); ?></div><?php endif; ?>
            </form>
            <div class="small" style="margin-top:8px;color:var(--muted)">Pastikan file ini tidak dapat diakses publik jika tidak diinginkan.</div>
        </div>
    <?php else: ?>

    <?php if (!empty($messages)): foreach ($messages as $m): ?>
        <div class="alert ok card"><?php echo htmlspecialchars($m); ?></div>
    <?php endforeach; endif; ?>
    <?php if (!empty($errors)): foreach ($errors as $e): ?>
        <div class="alert err card"><?php echo htmlspecialchars($e); ?></div>
    <?php endforeach; endif; ?>

    <div class="card" style="margin-bottom:12px">
        <div class="breadcrumbs">
            <?php foreach ($breadcrumbs as $i => $c): 
                $q = ['path' => $c['path']];
                $href = $_SERVER['PHP_SELF'] . '?' . http_build_query($q);
            ?>
                <?php if ($i) echo ' / '; ?>
                <a class="small" style="color:#9fbfcb;text-decoration:none" href="<?php echo $href; ?>"><?php echo htmlspecialchars($c['label']); ?></a>
            <?php endforeach; ?>
        </div>

        <div style="display:flex;justify-content:space-between;gap:12px;flex-wrap:wrap">
            <div class="form-inline">
                <!-- Upload -->
                <form class="form-inline" method="post" enctype="multipart/form-data" style="display:inline-flex;gap:8px">
                    <input type="hidden" name="action" value="upload">
                    <input type="hidden" name="csrf" value="<?php echo $_SESSION['csrf_token']; ?>">
                    <input class="input" type="file" name="upload_file" required>
                    <button class="btn" type="submit">Upload</button>
                </form>

                <!-- New Folder -->
                <form method="post" class="form-inline" style="display:inline-flex;gap:8px">
                    <input type="hidden" name="action" value="mkdir">
                    <input type="hidden" name="csrf" value="<?php echo $_SESSION['csrf_token']; ?>">
                    <input class="input" name="dirname" placeholder="Nama folder baru">
                    <button class="btn-ghost" type="submit">Buat Folder</button>
                </form>
            </div>

            <div class="small">Server waktu: <?php echo date('Y-m-d H:i:s'); ?></div>
        </div>
    </div>

    <div class="card">
        <table class="table">
            <thead>
                <tr>
                    <th>Nama</th>
                    <th>Ukuran</th>
                    <th>Diubah</th>
                    <th>Perms</th>
                    <th>Aksi</th>
                </tr>
            </thead>
            <tbody>
                <?php if ($cwd !== '/' && is_dir(dirname($cwd))): // link ke parent ?>
                    <tr class="row">
                        <td colspan="5"><a href="<?php echo $_SERVER['PHP_SELF'] . '?path=' . urlencode(dirname($cwd)); ?>">.. (Parent)</a></td>
                    </tr>
                <?php endif; ?>
                <?php foreach ($entries as $e): 
                    $name = $e['name'];
                    $is_dir = $e['is_dir'];
                    $size = $e['size'];
                    $mtime = date('Y-m-d H:i:s', $e['mtime']);
                    $perms = $e['perms'];
                    $encoded = urlencode($name);
                    ?>
                    <tr class="row">
                        <td>
                            <?php if ($is_dir): ?>
                                <a href="<?php echo $_SERVER['PHP_SELF'] . '?path=' . urlencode($cwd . '/' . $name); ?>">📁 <?php echo htmlspecialchars($name); ?></a>
                            <?php else: ?>
                                📄 <?php echo htmlspecialchars($name); ?>
                            <?php endif; ?>
                        </td>
                        <td><?php echo $is_dir ? '-' : filesize_human($size); ?></td>
                        <td><?php echo $mtime; ?></td>
                        <td><?php echo $perms; ?></td>
                        <td class="actions">
                            <?php if (!$is_dir): ?>
                                <a class="btn-ghost" href="<?php echo $_SERVER['PHP_SELF'] . '?path=' . urlencode($cwd) . '&download=' . urlencode($name); ?>">Download</a>
                                <a class="btn-ghost" href="<?php echo $_SERVER['PHP_SELF'] . '?path=' . urlencode($cwd) . '&edit=' . urlencode($name); ?>">Edit</a>
                            <?php endif; ?>

                            <!-- Rename form -->
                            <form method="post" style="display:inline-flex" onsubmit="return confirm('Yakin mengganti nama?');">
                                <input type="hidden" name="action" value="rename">
                                <input type="hidden" name="csrf" value="<?php echo $_SESSION['csrf_token']; ?>">
                                <input type="hidden" name="old" value="<?php echo htmlspecialchars($name); ?>">
                                <input class="input" name="new" placeholder="Nama baru" style="width:120px">
                                <button class="btn-ghost" type="submit">Rename</button>
                            </form>

                            <!-- Chmod -->
                            <form method="post" style="display:inline-flex" onsubmit="return confirm('Ubah permission?');">
                                <input type="hidden" name="action" value="chmod">
                                <input type="hidden" name="csrf" value="<?php echo $_SESSION['csrf_token']; ?>">
                                <input type="hidden" name="target" value="<?php echo htmlspecialchars($name); ?>">
                                <input class="input" name="mode" placeholder="0755" style="width:80px">
                                <button class="btn-ghost" type="submit">Chmod</button>
                            </form>

                            <!-- Delete -->
                            <form method="post" style="display:inline-flex" onsubmit="return confirm('Hapus item ini?');">
                                <input type="hidden" name="action" value="delete">
                                <input type="hidden" name="csrf" value="<?php echo $_SESSION['csrf_token']; ?>">
                                <input type="hidden" name="target" value="<?php echo htmlspecialchars($name); ?>">
                                <button class="btn-ghost" type="submit">Delete</button>
                            </form>

                            <!-- Copy / Move short forms -->
                            <form method="post" style="display:inline-flex">
                                <input type="hidden" name="csrf" value="<?php echo $_SESSION['csrf_token']; ?>">
                                <input type="hidden" name="src" value="<?php echo htmlspecialchars($name); ?>">
                                <input class="input" name="dst" placeholder="tujuan relatif (ex: ../folder/file)" style="width:160px">
                                <button class="btn-ghost" type="submit" formaction="<?php echo $_SERVER['PHP_SELF']; ?>?do=copy" name="action" value="copy">Copy</button>
                                <button class="btn-ghost" type="submit" formaction="<?php echo $_SERVER['PHP_SELF']; ?>?do=move" name="action" value="move">Move</button>
                            </form>
                        </td>
                    </tr>
                <?php endforeach; ?>
            </tbody>
        </table>
    </div>

    <!-- Edit area -->
    <?php
    if (isset($_GET['edit']) && is_logged_in()) {
        $editName = $_GET['edit'];
        $editPath = resolve_path($cwd, $editName);
        if ($editPath && is_file($editPath) && is_readable($editPath)) {
            $content = file_get_contents($editPath);
            ?>
            <div class="card" style="margin-top:12px">
                <h3>Edit: <?php echo htmlspecialchars($editName); ?></h3>
                <form method="post">
                    <input type="hidden" name="action" value="savefile">
                    <input type="hidden" name="csrf" value="<?php echo $_SESSION['csrf_token']; ?>">
                    <input type="hidden" name="file" value="<?php echo htmlspecialchars($editName); ?>">
                    <textarea name="content"><?php echo htmlspecialchars($content); ?></textarea>
                    <div style="margin-top:8px">
                        <button class="btn" type="submit">Simpan</button>
                    </div>
                </form>
            </div>
            <?php
        } else {
            echo '<div class="alert err card">File tidak dapat dibuka untuk diedit.</div>';
        }
    }
    ?>

    <!-- Download handling -->
    <?php
    if (isset($_GET['download']) && is_logged_in()) {
        $dl = $_GET['download'];
        $dlpath = resolve_path($cwd, $dl);
        if ($dlpath && is_file($dlpath) && is_readable($dlpath)) {
            header('Content-Description: File Transfer');
            header('Content-Type: application/octet-stream');
            header('Content-Disposition: attachment; filename="'.basename($dlpath).'"');
            header('Content-Length: ' . filesize($dlpath));
            readfile($dlpath);
            exit;
        } else {
            echo '<div class="alert err card">File tidak tersedia untuk didownload.</div>';
        }
    }
    ?>

    <div class="footer">Dibuat untuk penggunaan internal. <?php echo htmlspecialchars($_SERVER['SERVER_SOFTWARE'] ?? 'Server'); ?></div>

    <?php endif; // end logged in ?>
</div>
</body>
</html>
